package com.hqd.ch03.v49.context.annotation;

import com.hqd.ch03.utils.ClassUtils;
import com.hqd.ch03.v49.aware.ResourceLoaderAware;
import com.hqd.ch03.v49.beans.factory.BeanDefinitionStoreException;
import com.hqd.ch03.v49.beans.factory.Lookup;
import com.hqd.ch03.v49.beans.factory.config.BeanDefinition;
import com.hqd.ch03.v49.context.index.CandidateComponentsIndex;
import com.hqd.ch03.v49.context.index.CandidateComponentsIndexLoader;
import com.hqd.ch03.v49.context.stereotype.Component;
import com.hqd.ch03.v49.core.annotation.AnnotationUtils;
import com.hqd.ch03.v49.core.env.Environment;
import com.hqd.ch03.v49.core.env.EnvironmentCapable;
import com.hqd.ch03.v49.core.env.StandardEnvironment;
import com.hqd.ch03.v49.core.type.AnnotationMetadata;
import com.hqd.ch03.v49.core.type.classreading.CachingMetadataReaderFactory;
import com.hqd.ch03.v49.core.type.classreading.MetadataReader;
import com.hqd.ch03.v49.core.type.classreading.MetadataReaderFactory;
import com.hqd.ch03.v49.core.type.filter.AnnotationTypeFilter;
import com.hqd.ch03.v49.core.type.filter.AssignableTypeFilter;
import com.hqd.ch03.v49.core.type.filter.TypeFilter;
import com.hqd.ch03.v49.factory.annotation.AnnotatedBeanDefinition;
import com.hqd.ch03.v49.io.Resource;
import com.hqd.ch03.v49.io.ResourceLoader;
import com.hqd.ch03.v49.io.support.PathMatchingResourcePatternResolver;
import com.hqd.ch03.v49.io.support.ResourcePatternResolver;
import com.hqd.ch03.v49.io.support.ResourcePatternUtils;
import com.hqd.ch03.v49.registry.BeanDefinitionRegistry;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.*;

public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private final List<TypeFilter> includeFilters = new ArrayList<>();
    private final List<TypeFilter> excludeFilters = new ArrayList<>();
    private String resourcePattern = DEFAULT_RESOURCE_PATTERN;

    private Environment environment;


    private ConditionEvaluator conditionEvaluator;


    private ResourcePatternResolver resourcePatternResolver;


    private MetadataReaderFactory metadataReaderFactory;


    private CandidateComponentsIndex componentsIndex;


    /**
     * Protected constructor for flexible subclass initialization.
     *
     * @since 4.3.6
     */
    protected ClassPathScanningCandidateComponentProvider() {
    }

    public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
        this(useDefaultFilters, new StandardEnvironment());
    }

    public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
        if (useDefaultFilters) {
            registerDefaultFilters();
        }
        setEnvironment(environment);
        setResourceLoader(null);
    }


    public void setResourcePattern(String resourcePattern) {
        this.resourcePattern = resourcePattern;
    }

    /**
     * Add an include type filter to the <i>end</i> of the inclusion list.
     */
    public void addIncludeFilter(TypeFilter includeFilter) {
        this.includeFilters.add(includeFilter);
    }

    /**
     * Add an exclude type filter to the <i>front</i> of the exclusion list.
     */
    public void addExcludeFilter(TypeFilter excludeFilter) {
        this.excludeFilters.add(0, excludeFilter);
    }

    public void resetFilters(boolean useDefaultFilters) {
        this.includeFilters.clear();
        this.excludeFilters.clear();
        if (useDefaultFilters) {
            registerDefaultFilters();
        }
    }

    @SuppressWarnings("unchecked")
    protected void registerDefaultFilters() {
        this.includeFilters.add(new AnnotationTypeFilter(Component.class));
        ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
        try {
            this.includeFilters.add(new AnnotationTypeFilter(
                    ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
        } catch (ClassNotFoundException ex) {
            // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
        }
        try {
            this.includeFilters.add(new AnnotationTypeFilter(
                    ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
        } catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
    }

    @Override
    public final Environment getEnvironment() {
        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }
        return this.environment;
    }

    /**
     * Set the Environment to use when resolving placeholders and evaluating
     * {@link Conditional @Conditional}-annotated component classes.
     * <p>The default is a {@link StandardEnvironment}.
     *
     * @param environment the Environment to use
     */
    public void setEnvironment(Environment environment) {
        this.environment = environment;
        this.conditionEvaluator = null;
    }

    /**
     * Return the {@link BeanDefinitionRegistry} used by this scanner, if any.
     */

    protected BeanDefinitionRegistry getRegistry() {
        return null;
    }

    /**
     * Return the ResourceLoader that this component provider uses.
     */
    public final ResourceLoader getResourceLoader() {
        return getResourcePatternResolver();
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
        this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader());
    }

    private ResourcePatternResolver getResourcePatternResolver() {
        if (this.resourcePatternResolver == null) {
            this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        return this.resourcePatternResolver;
    }

    /**
     * Return the MetadataReaderFactory used by this component provider.
     */
    public final MetadataReaderFactory getMetadataReaderFactory() {
        if (this.metadataReaderFactory == null) {
            this.metadataReaderFactory = new CachingMetadataReaderFactory();
        }
        return this.metadataReaderFactory;
    }

    /**
     * Set the {@link MetadataReaderFactory} to use.
     * <p>Default is a {@link CachingMetadataReaderFactory} for the specified
     * {@linkplain #setResourceLoader resource loader}.
     * <p>Call this setter method <i>after</i> {@link #setResourceLoader} in order
     * for the given MetadataReaderFactory to override the default factory.
     */
    public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) {
        this.metadataReaderFactory = metadataReaderFactory;
    }

    public Set<BeanDefinition> findCandidateComponents(String basePackage) {

        if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
            return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
        } else {
            return scanCandidateComponents(basePackage);
        }
    }

    /**
     * Determine if the index can be used by this instance.
     *
     * @return {@code true} if the index is available and the configuration of this
     * instance is supported by it, {@code false} otherwise
     * @since 5.0
     */
    private boolean indexSupportsIncludeFilters() {
        for (TypeFilter includeFilter : this.includeFilters) {
            if (!indexSupportsIncludeFilter(includeFilter)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Determine if the specified include {@link TypeFilter} is supported by the index.
     *
     * @param filter the filter to check
     * @return whether the index supports this include filter
     * @see #extractStereotype(TypeFilter)
     * @since 5.0
     */
    private boolean indexSupportsIncludeFilter(TypeFilter filter) {
        if (filter instanceof AnnotationTypeFilter) {
            Class<? extends Annotation> annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
            return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
                    annotation.getName().startsWith("javax."));
        }
        if (filter instanceof AssignableTypeFilter) {
            Class<?> target = ((AssignableTypeFilter) filter).getTargetType();
            return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
        }
        return false;
    }

    /**
     * Extract the stereotype to use for the specified compatible filter.
     *
     * @param filter the filter to handle
     * @return the stereotype in the index matching this filter
     * @see #indexSupportsIncludeFilter(TypeFilter)
     * @since 5.0
     */

    private String extractStereotype(TypeFilter filter) {
        if (filter instanceof AnnotationTypeFilter) {
            return ((AnnotationTypeFilter) filter).getAnnotationType().getName();
        }
        if (filter instanceof AssignableTypeFilter) {
            return ((AssignableTypeFilter) filter).getTargetType().getName();
        }
        return null;
    }

    private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            Set<String> types = new HashSet<>();
            for (TypeFilter filter : this.includeFilters) {
                String stereotype = extractStereotype(filter);
                if (stereotype == null) {
                    throw new IllegalArgumentException("Failed to extract stereotype from " + filter);
                }
                types.addAll(index.getCandidateTypes(basePackage, stereotype));
            }
            for (String type : types) {
                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type);
                if (isCandidateComponent(metadataReader)) {
                    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                    sbd.setSource(metadataReader.getResource());
                    if (isCandidateComponent(sbd)) {
                        candidates.add(sbd);
                    }
                }

            }
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return candidates;
    }

    private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            /**
             * 扫描类路径下所有资源
             */
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            for (Resource resource : resources) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    if (isCandidateComponent(metadataReader)) {
                        //生成扫描来源的BD
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setSource(resource);
                        if (isCandidateComponent(sbd)) {
                            candidates.add(sbd);
                        }
                    }
                } catch (FileNotFoundException ex) {
                    ex.printStackTrace();
                } catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                            "Failed to read candidate component class: " + resource, ex);
                }
            }
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return candidates;
    }


    /**
     * Resolve the specified base package into a pattern specification for
     * the package search path.
     * <p>The default implementation resolves placeholders against system properties,
     * and converts a "."-based package path to a "/"-based resource path.
     *
     * @param basePackage the base package as specified by the user
     * @return the pattern specification to be used for package searching
     */
    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(getEnvironment().resolveRequiredPlaceholders(basePackage));
    }

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            /**
             * 是否自动注入
             */
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

    /**
     * Determine whether the given class is a candidate component based on any
     * {@code @Conditional} annotations.
     *
     * @param metadataReader the ASM ClassReader for the class
     * @return whether the class qualifies as a candidate component
     */
    private boolean isConditionMatch(MetadataReader metadataReader) {
        if (this.conditionEvaluator == null) {
            this.conditionEvaluator =
                    new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
        }
        return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
    }

    /**
     * Determine whether the given bean definition qualifies as candidate.
     * <p>The default implementation checks whether the class is not an interface
     * and not dependent on an enclosing class.
     * <p>Can be overridden in subclasses.
     *
     * @param beanDefinition the bean definition to check
     * @return whether the bean definition qualifies as a candidate component
     */
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return (metadata.isIndependent() && (metadata.isConcrete() ||
                (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
    }


    /**
     * Clear the local metadata cache, if any, removing all cached class metadata.
     */
    public void clearCache() {
        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            // Clear cache in externally provided MetadataReaderFactory; this is a no-op
            // for a shared cache since it'll be cleared by the ApplicationContext.
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }

}

